#ifndef TC_DB_MANAGER_HPP
#define TC_DB_MANAGER_HPP

#include <boost/graph/adjacency_list.hpp>
#include <string>
#include <iostream>

using std::string;
using namespace boost;

//////////////////////////////////////////////////////////
// Graph class used in the database manager
//   using bundled properties to associate information
//   for vertices and edges
//   [http://www.boost.org/libs/graph/doc/bundles.html]

struct Node
{
  string IP;
  string MAC;
  bool TPC;
  int MAX_POWER;
  double Toc; // time-out counter
};

struct Link
{
  double Weight;
};

typedef boost::adjacency_list<
  boost::listS, boost::listS, boost::bidirectionalS,
  Node, Link> Graph;


//////////////////////////////////////////////////////////
// Topology Database Manager class

class TCDatabaseManager{
public:
  // Default constructor
  TCDatabaseManager() {isReady = false; Tic = 0; T = 5.0;}
  
  // Set node time-out value
  void SetTimeOut(double time_out) {T = time_out;}

  // Check if the graph is ready for use
  bool Ready() {return isReady;}
  
  // Get number of nodes in the graph
  int NumberOfNodes() {return num_vertices(G);}

  // Add a new node or update existing node information (including the timer)
  void AddNode(string ip, string mac, bool tpc, int max_power);

  // Add a new edge (u,v) or update existing edge information
  bool AddEdge(string source, string dest);

  // Add a new edge (u,v) or update existing edge information; includes weight parameter
  bool AddEdge(string source, string dest, double weight);

  // Add a set of in_edges towards node u
  // Note that in_edges(u) is first deleted, then reconstructed
  template <typename NeighborList>
  bool AddNeighborhood(string ip, NeighborList& ip_list);

  // Add a set of in_edges towards node u
  // Same as above, but adds the edge weights
  template <typename NeighborList, typename WeightList>
  bool AddNeighborhood(string ip, NeighborList& ip_list, WeightList& w_list);

  // Get the size of the list of neighbors for node u
  int GetNumberOfNeighbors(string ip);

  // Get the size of the list of neighbors for node u (indexed version)
  int GetNumberOfNeighbors(int index);

  // Get the list of neighbors for node u
  template <typename NeighborList>
  bool GetNeighborhood(string ip, NeighborList& ip_list);

  // Get the list of neighbors for node u with the respective in_edge weights
  template <typename NeighborList, typename WeightList>
  bool GetNeighborhood(string ip, NeighborList& ip_list, WeightList& w_list);

  // Get information about node[index]
  bool GetNodeInfo(int index, string& ip, string& mac,
		   bool& tpc, int& max_power);

  // Get information about node indexed by IP address
  bool GetNodeInfo(string source_ip, string& ip, string& mac,
		   bool& tpc, int& max_power);

  // Get graph
  bool GetGraph(Graph& GG);
  
  // Print graph on the standard output
  void PrintGraph();

  // Update timer variable (this function should be called periodically)
  // Note: calling UpdateGraph() too often may cause too much overhead
  void UpdateTimer(double time) {Tic = time; UpdateGraph();}

private:
  Graph G;
  bool isReady;
  double T; // Time-out value in seconds
  double Tic; // Final "time" for time-out calculations
  
  // Check graph for completeness (all edges should have weights)
  bool CheckGraph();

  // Get reference to node with given IP address
  std::pair<graph_traits<Graph>::vertex_descriptor, bool>
  GetNodeByIP(string ip);

  // Remove node by IP address
  bool RemoveNodeByIP(string ip);

  // Remove node by reference
  void RemoveNode(graph_traits<Graph>::vertex_descriptor v);

  // Reset node timer
  void ResetNodeTimer(graph_traits<Graph>::vertex_descriptor v);

  // Update graph: delete expired nodes and all associated edges
  void UpdateGraph();
};


// --- Member Functions ---

/////////
void TCDatabaseManager::AddNode(string ip, string mac,
			       bool tpc, int max_power)
{
  graph_traits<Graph>::vertex_descriptor v;
  bool exist;

  tie(v, exist) = GetNodeByIP(ip);
  if (!exist){
    v = add_vertex(G);
  }

  G[v].IP = ip;
  G[v].MAC = mac;
  G[v].TPC = tpc;
  G[v].MAX_POWER = max_power;
  G[v].Toc = Tic; // reset timer
}

/////////
bool TCDatabaseManager::AddEdge(string source, string dest)
{
  return AddEdge(source, dest, -1.0);
}

/////////
bool TCDatabaseManager::AddEdge(string source, string dest, double weight)
{
  graph_traits<Graph>::vertex_descriptor u, v;
  graph_traits<Graph>::edge_descriptor e;
  graph_traits<Graph>::out_edge_iterator ed, ed_end;
  bool u_exists, v_exists, inserted;

  tie(u, u_exists) = GetNodeByIP(source);
  tie(v, v_exists) = GetNodeByIP(dest);

  // Both the source and target nodes SHOULD  exist
  // (otherwise, AddNode() should have been previously called)
  // Note that the neighborhood graph contains only nodes that are my 1-hop neighbor nodes
  if (!(u_exists && v_exists))
    return false;

  // If both nodes exist...
  for (tie(ed, ed_end) = out_edges(u, G); ed != ed_end; ++ed){
    //If the edge (u,v) already exists, update the weight property only
    if (v == target(*ed, G)){
      G[*ed].Weight = weight;
      return true;
    }
  }
  // If the edge does not yet exist, create it
  tie(e, inserted) = add_edge(u, v, G);
  G[e].Weight = weight;
  return inserted;
}

////////
template <typename NeighborList>
bool TCDatabaseManager::AddNeighborhood(string ip, NeighborList& ip_list)
{
  std::vector<double> w_list(ip_list.size());
  std::vector<double>::iterator iter;

  for (unsigned int i = 0; i < w_list.size(); ++i){
    w_list[i] = -1.0;
  }

  return AddNeighborhood(ip, ip_list, w_list);
}

////////
template <typename NeighborList, typename WeightList>
bool TCDatabaseManager::AddNeighborhood(string ip,
					NeighborList& ip_list,
					WeightList& w_list)
{
  graph_traits<Graph>::vertex_descriptor u, v;
  graph_traits<Graph>::edge_descriptor e;
  typename NeighborList::iterator si;
  typename WeightList::iterator wi;
  bool exist;

  tie(v, exist) = GetNodeByIP(ip);
  if (!exist)
    return false;
  
  clear_in_edges(v, G);

  // Add new neighborhood edges for node u
  wi = w_list.begin();
  for (si = ip_list.begin(); si != ip_list.end(); ++si, ++wi){
    tie(u, exist) = GetNodeByIP(*si);
    if (exist){
      AddEdge(*si, ip, *wi);
    }
  }
  return true;
}

////////
int TCDatabaseManager::GetNumberOfNeighbors(string ip)
{
  graph_traits<Graph>::vertex_descriptor v;
  //  graph_traits<Graph>::in_edge_iterator ed, ed_end;
  bool exist;
  int num_nbr = 0;

  tie(v, exist) = GetNodeByIP(ip);
  if (!exist)
    return -1;

  /*for (tie(ed, ed_end) = in_edges(v, G); ed != ed_end; ++ed){
    ++num_nbr;
    }*/

  num_nbr = in_degree(v, G);

  return num_nbr;
}

////////
int TCDatabaseManager::GetNumberOfNeighbors(int index)
{
  graph_traits<Graph>::vertex_descriptor v;
  //  graph_traits<Graph>::in_edge_iterator ed, ed_end;
  int num_nbr = 0;

  if (index >= NumberOfNodes())
    return -1;

  v = vertex(index, G);

  /*  for (tie(ed, ed_end) = in_edges(v, G); ed != ed_end; ++ed){
    ++num_nbr;
    }*/

  num_nbr = in_degree(v, G);

  return num_nbr;
}

////////
template <typename NeighborList>
bool TCDatabaseManager::GetNeighborhood(string ip, NeighborList& ip_list)
{
  graph_traits<Graph>::vertex_descriptor v;
  graph_traits<Graph>::in_edge_iterator ed, ed_end;
  typename NeighborList::iterator si;
  bool exist;

  tie(v, exist) = GetNodeByIP(ip);
  if (!exist)
    return false;

  ip_list.clear();

  for (tie(ed, ed_end) = in_edges(v, G); ed != ed_end; ++ed){
    ip_list.push_back(G[source(*ed, G)].IP);
  }
  return true;
}

////////
template <typename NeighborList, typename WeightList>
bool TCDatabaseManager::GetNeighborhood(string ip,
					NeighborList& ip_list,
					WeightList& w_list)
{
  graph_traits<Graph>::vertex_descriptor v;
  graph_traits<Graph>::in_edge_iterator ed, ed_end;
  typename NeighborList::iterator si;
  bool exist;

  tie(v, exist) = GetNodeByIP(ip);
  if (!exist)
    return false;

  ip_list.clear();
  w_list.clear();

  for (tie(ed, ed_end) = in_edges(v, G); ed != ed_end; ++ed){
    ip_list.push_back(G[source(*ed, G)].IP);
    w_list.push_back(G[*ed].Weight);
  }
  return true;
}


////////
bool TCDatabaseManager::GetNodeInfo(int index, string& ip, string& mac,
				   bool& tpc, int& max_power)
{
  graph_traits<Graph>::vertex_descriptor v;

  if (index >= NumberOfNodes())
    return false;

  v = vertex(index, G);

  ip = G[v].IP;
  mac = G[v].MAC;
  tpc = G[v].TPC;
  max_power = G[v].MAX_POWER;

  return true;
}

////////
bool TCDatabaseManager::GetNodeInfo(string source_ip, string& ip, string& mac,
				   bool& tpc, int& max_power)
{
  graph_traits<Graph>::vertex_descriptor v;
  bool exist;

  tie(v, exist) = GetNodeByIP(source_ip);
  if (!exist)
    return false;

  ip = G[v].IP;
  mac = G[v].MAC;
  tpc = G[v].TPC;
  max_power = G[v].MAX_POWER;

  return true;
}

/////////
void TCDatabaseManager::PrintGraph()
{
  graph_traits<Graph>::vertex_iterator vi, vi_end;
  graph_traits<Graph>::edge_iterator ed, ed_end;

  std::cout << std::endl << "vertices(G)" << std::endl;
  std::cout <<              "-----------" << std::endl;
  
  for (tie(vi, vi_end) = vertices(G); vi != vi_end; ++vi){
    std::cout << G[*vi].IP << " ; ";
    std::cout << G[*vi].MAC << " ; ";
    std::cout << "TPC: " << (G[*vi].TPC? "yes" : " no") << " ; ";
    std::cout << "Max_Power: " << G[*vi].MAX_POWER << " dBm" << " ; ";
    std::cout << "Time-out in " << (T - (Tic - G[*vi].Toc)) << " sec";
    std::cout << std::endl;
  }
  std::cout << std::endl;

  std::cout << "  edges(G)  " << std::endl;
  std::cout << "------------" << std::endl;

  graph_traits<Graph>::vertex_descriptor u, v;

  for (tie(ed, ed_end) = edges(G); ed != ed_end; ++ed){
    u = source(*ed, G);
    v = target(*ed, G);
    std::cout << "Weight(" << G[u].IP << " , " << G[v].IP << ") = ";
    std::cout << G[*ed].Weight << std::endl;
  }
  std::cout << std::endl;
}

/////////
bool TCDatabaseManager::GetGraph(Graph& GG)
{
  // The graph must be ready in order to be copied
  if (CheckGraph() == false)
    return false;

  GG = G; // operator= makes a copy of the Graph object
  return true;
}

/////////
bool TCDatabaseManager::CheckGraph()
{
  graph_traits<Graph>::edge_iterator ed, ed_end;

  tie(ed, ed_end) = edges(G);

  // The graph is not ready if there are no edges
  if (ed == ed_end){
    return false;
  }

  // It is also not ready if at least one edge
  // does not have a valid weight value
  for (; ed != ed_end; ++ed){
    if (G[*ed].Weight < 0)
      return false;
  }

  return true;
}

////////
std::pair<graph_traits<Graph>::vertex_descriptor, bool>
TCDatabaseManager::GetNodeByIP(string ip)
{
  graph_traits<Graph>::vertex_iterator vi, vi_end;
  graph_traits<Graph>::vertex_descriptor v;
  std::pair<graph_traits<Graph>::vertex_descriptor, bool> p;

  p.second = false;

  for (tie(vi, vi_end) = vertices(G); vi != vi_end; ++vi){
    v = *vi;
    if (G[v].IP == ip){
      p.first = v; p.second = true;
      return p;
    }
  }

  p.first = *vi_end;
  return p;
}

/////////
bool TCDatabaseManager::RemoveNodeByIP(string ip)
{
  graph_traits<Graph>::vertex_descriptor v;
  bool exist;

  tie(v, exist) = GetNodeByIP(ip);

  if (exist){
    RemoveNode(v);
    return true;
  }

  return false;
}

//////////
void TCDatabaseManager::ResetNodeTimer(graph_traits<Graph>::vertex_descriptor v)
{
  G[v].Toc = Tic;
}

//////////
void TCDatabaseManager::RemoveNode(graph_traits<Graph>::vertex_descriptor v)
{
  clear_vertex(v, G); // Remove all edges to and from vertex v
  remove_vertex(v, G); // Remove vertex v from the vertex set of the graph
}

/////////
void TCDatabaseManager::UpdateGraph()
{
  graph_traits<Graph>::vertex_iterator vi, vi_end;

  // Find nodes whose timers have expired and remove them from the Graph
  tie(vi, vi_end) = vertices(G);
  
  // Reset timer for the first node (the root node's timer should never expire)
  ResetNodeTimer(*vi);

  while (vi != vi_end){
    if ( (Tic - G[*vi].Toc) > T ){
      RemoveNode(*vi);
      tie(vi, vi_end) = vertices(G);
      continue;
    }
    ++vi;
  }

  /*  for (tie(vi, vi_end) = vertices(G); vi != vi_end; ++vi){
    if ( (Tic - G[*vi].Toc) > T ){
      // Note that remove_vertex() invalidates the vertex iterator if the vector list is vecS
      RemoveNode(*vi);
    }
    }*/
}



#endif // TC_DB_MANAGER_HPP
